package thead;


import java.util.PriorityQueue;

/**
 * 模拟实现定时器
 */

class MyTimer implements Comparable<MyTimer>{
    private Runnable runnable;// 线程所需要执行的任务
    private long time;// 指定的时间 单位 ms
    public MyTimer(Runnable runnable,long time) {
        this.runnable = runnable;// 添加当前的任务
        this.time = System.currentTimeMillis() + time;// 这里加上现在的时间,是为了方便后面和系统时间比较
    }
    public Runnable getRunnable() {
        return runnable;
    }
    public long getTime() {
        return time;
    }

    @Override
    public int compareTo(MyTimer o) {
        return (int)(this.time - o.time);
    }
}
class MyTimerTask {
    PriorityQueue<MyTimer> queue = new PriorityQueue<>();// 优先级队列
    Object locker = new Object();
    public void schedule(Runnable runnable, long time) {
        synchronized (locker) {
            queue.offer(new MyTimer(runnable,time));// 将当前的任务和时间实例化,添加到优先级队列当中
            locker.notify();// 唤醒线程
        }
    }
    // 扫描类
    public MyTimerTask() {
        // 创建一个扫描线程
        Thread thread = new Thread(() -> {
            while(true) {
                try {
                    synchronized (locker) {
                        // 这里不使用if 而使用while的原因是
                        // 当线程被唤醒的时候,再判断一下是否满足条件 当前队列内是否有元素,防止被意外唤醒
                        while(queue.isEmpty()) {
                            // 当前队列是空的 堵塞线程,直到线程被唤醒
                                locker.wait();
                        }
                        // 当前线程已经被唤醒, 队列已经不为空,接下来开始比较
                        MyTimer myTimer = queue.peek(); // 查看当前 时间 最近的堆顶元素
                        long curTime = System.currentTimeMillis();// 获取当前系统时间
                        if(curTime >= myTimer.getTime()) {// 当前时间已经 大于等于 预定时间 执行任务
                            myTimer.getRunnable().run();
                            queue.poll();// 任务已执行,弹出优先级队列
                        }else {
                            // 这里是 队列已经有任务了,但是没到时间, 我们这里不能什么都不管,需要 处理一下,否则会出现忙等问题
                            locker.wait(myTimer.getTime() - curTime);
                            // 这里使用wait 不使用sleep 的原因是:
                            // 如果再有新的任务添加进来,线程就会被唤醒,然后再重新判断
                            // 假如 当前队列内的 堆顶任务的执行时间是 14:05
                            // 新任务执行时间为14:02,那就会添加到队列中
                            // 然后线程就会被唤醒,然后按照新任务的时间进行等待
                        }
                    }
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        thread.start();
    }
}
public class Demo20 {
    public static void main(String[] args) {
        MyTimerTask task = new MyTimerTask();
        task.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("3000ms");
            }
        },3000);
        task.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("2000ms");
            }
        },2000);
        task.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("1000ms");
            }
        },1000);
        System.out.println("启动定时器");
    }
}
